# DDLDeployer.java

    package org.codefilarete.stalactite.sql.ddl;

    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Collection;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Set;
    import java.util.TreeSet;

    import org.codefilarete.stalactite.engine.PersistenceContext;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredPersister;
    import org.codefilarete.stalactite.mapping.id.manager.BeforeInsertIdentifierManager;
    import org.codefilarete.stalactite.mapping.id.manager.IdentifierInsertionManager;
    import org.codefilarete.stalactite.mapping.id.sequence.DatabaseSequenceSelector;
    import org.codefilarete.stalactite.sql.ConnectionProvider;
    import org.codefilarete.stalactite.sql.ConnectionProvider.DataSourceConnectionProvider;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ServiceLoaderDialectResolver;
    import org.codefilarete.stalactite.sql.ddl.structure.Sequence;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.SQLExecutionException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import static org.codefilarete.stalactite.sql.ddl.structure.Table.COMPARATOR_ON_SCHEMA_AND_NAME;

    /**
     * A class aimed at deploying DDL elements to a database. It gets its elements from a {@link DDLGenerator} and execute them
     * onto a {@link ConnectionProvider}, so it's more an entry point for high level usage.
     *
     * @author Guillaume Mary
     * @see #deployDDL()
     * @see #dropDDL()
     * @see DDLGenerator
     * @see DDLSequenceGenerator
     */
    public class DDLDeployer {

    	private static final Logger LOGGER = LoggerFactory.getLogger(DDLDeployer.class);

    	/**
    	 * Collect tables defined in the given {@link PersistenceContext}
    	 *
    	 * @param persistenceContext a {@link PersistenceContext} to scan for tables
    	 * @return a {@link Collection} of found tables
    	 */
    	public static Collection<Table<?>> collectTables(PersistenceContext persistenceContext) {
    		Set<Table<?>> result = new TreeSet<>(COMPARATOR_ON_SCHEMA_AND_NAME);
    		persistenceContext.getPersisters().forEach(p -> result.addAll(((ConfiguredPersister<?, ?>) p).giveImpliedTables()));
    		return result;
    	}

    	public static Collection<Sequence> collectSequences(PersistenceContext persistenceContext) {
    		Set<Sequence> result = new LinkedHashSet<>(20);
    		persistenceContext.getPersisters().forEach(p -> {
    			IdentifierInsertionManager<?, ?> identifierInsertionManager = ((ConfiguredPersister<?, ?>) p).getMapping().getIdMapping().getIdentifierInsertionManager();
    			if (identifierInsertionManager instanceof BeforeInsertIdentifierManager
    					&& ((BeforeInsertIdentifierManager<?, ?>) identifierInsertionManager).getIdentifierFixer().getSequence() instanceof DatabaseSequenceSelector) {
    				DatabaseSequenceSelector databaseSequenceSelector = (DatabaseSequenceSelector) ((BeforeInsertIdentifierManager<?, ?>) identifierInsertionManager).getIdentifierFixer().getSequence();
    				result.add(databaseSequenceSelector.getDatabaseSequence());
    			}
    		});
    		return result;
    	}

    	private final ConnectionProvider connectionProvider;

    	private final DDLGenerator ddlGenerator;

    	/**
    	 * Simple constructor that gets its information from the given {@link PersistenceContext}: {@link DDLGenerator} from its
    	 * {@link Dialect} and {@link ConnectionProvider}.
    	 * It automatically adds the {@link PersistenceContext} tables and sequences.
    	 *
    	 * @param persistenceContext a {@link PersistenceContext}, source of arguments for {@link #DDLDeployer(DDLTableGenerator, DDLSequenceGenerator, ConnectionProvider)}
    	 * @see #DDLDeployer(DDLTableGenerator, DDLSequenceGenerator, ConnectionProvider)
    	 */
    	public DDLDeployer(PersistenceContext persistenceContext) {
    		this(persistenceContext.getDialect(), persistenceContext.getConnectionProvider());
    		ddlGenerator.addTables(collectTables(persistenceContext));
    		ddlGenerator.addSequences(collectSequences(persistenceContext));
    	}

    	/**
    	 * Simple constructor that gets its information from the given {@link Dialect}: {@link DDLGenerator} and {@link DDLSequenceGenerator}
    	 * Tables to deploy must be added through {@link #getDdlGenerator()}.addTables(..)
    	 *
    	 * @param dialect the {@link Dialect} to get SQL generators from
    	 * @param connectionProvider provider of {@link Connection} to execute SQL
    	 * @see #DDLDeployer(DDLTableGenerator, DDLSequenceGenerator, ConnectionProvider)
    	 */
    	public DDLDeployer(Dialect dialect, ConnectionProvider connectionProvider) {
    		this(dialect.getDdlTableGenerator(), dialect.getDdlSequenceGenerator(), connectionProvider);
    	}

    	/**
    	 * Constructor that gets its information from the given {@link DataSource}: the {@link Dialect} is found through {@link ServiceLoaderDialectResolver}.
    	 * Tables to deploy must be added through {@link #getDdlGenerator()}.addTables(..)
    	 *
    	 * @param dataSource the {@link DataSource} to get all information
    	 * @see #DDLDeployer(DDLTableGenerator, DDLSequenceGenerator, ConnectionProvider)
    	 */
    	public DDLDeployer(DataSource dataSource) {
    		ConnectionProvider connectionProvider = new DataSourceConnectionProvider(dataSource);
    		Dialect dialect;
    		try (Connection connection = connectionProvider.giveConnection()) {
    			dialect = new ServiceLoaderDialectResolver().determineDialect(connection);
    		} catch (SQLException e) {
    			throw new RuntimeException(e);
    		}
    		this.connectionProvider = connectionProvider;
    		this.ddlGenerator = new DDLGenerator(dialect.getDdlTableGenerator(), dialect.getDdlSequenceGenerator());
    	}

    	/**
    	 * Main constructor with mandatory objects for its work.
    	 * Tables to deploy must be added through {@link #getDdlGenerator()}.addTables(..)
    	 *
    	 * @param ddlTableGenerator the SQL scripts provider
    	 * @param connectionProvider the {@link Connection} provider for executing SQL scripts
    	 */
    	public DDLDeployer(DDLTableGenerator ddlTableGenerator, DDLSequenceGenerator ddlSequenceGenerator, ConnectionProvider connectionProvider) {
    		this.connectionProvider = connectionProvider;
    		this.ddlGenerator = new DDLGenerator(ddlTableGenerator, ddlSequenceGenerator);
    	}

    	public DDLGenerator getDdlGenerator() {
    		return ddlGenerator;
    	}

    	public void deployDDL() {
    		execute(getCreationScripts());
    	}

    	public List<String> getCreationScripts() {
    		return getDdlGenerator().getCreationScripts();
    	}

    	public void dropDDL() {
    		execute(getDropScripts());
    	}

    	public List<String> getDropScripts() {
    		return getDdlGenerator().getDropScripts();
    	}

    	protected void execute(List<String> sqls) {
    		Connection currentConnection = getCurrentConnection();
    		for (String sql : sqls) {
    			try (Statement statement = currentConnection.createStatement()) {
    				LOGGER.debug(sql);
    				statement.execute(sql);
    			} catch (SQLException t) {
    				throw new SQLExecutionException(sql, t);
    			}
    		}
    	}

    	protected Connection getCurrentConnection() {
    		return connectionProvider.giveConnection();
    	}
    }